// TODO: config macro
// if do #define TEST_IMPL
// then include this Test.h
// this will be header only file

#ifndef TEST_H // my own test framework, just call it framework
#define TEST_H

#include <string>
#include <iostream>
#include <cassert>
using std::cout;
using std::ostream;
using std::string;

#define test_(cond) do_test(cond, #cond, __FILE__, __LINE__)
#define fail_(str) do_fail(str, __FILE__, __LINE__)

namespace TestSuite
{
    class Test
    {
        ostream *osptr;
        long nPass;
        long nFail;
        // Disallow ctor
        Test(const Test &);
        Test &operator=(const Test &);

    protected:
        void do_test(bool cond, const string &lbl,
                     const char *fname, long lineno);
        void do_fail(const string &lbl, const char *fname,
                     long lineno);

    public:
        Test(ostream *optr = &cout)
        {
            osptr = optr;
            nPass = nFail = 0;
        }

        virtual ~Test() {}
        virtual void run() = 0; // interface
        long getNumPassed() const { return nPass; }
        long getNumFailed() const { return nFail; }
        const ostream *getStream() const { return osptr; }
        void setStream(ostream *optr)
        {
            this->osptr = optr;
        }
        void succeed_() { ++nPass; }
        long report() const;
        virtual void reset()
        {
            nPass = nFail = 0;
        }
    };
} // namespace TestSuite

#endif // TEST_H

#ifdef TEST_IMPL
#include <typeinfo>
using namespace std;
using namespace TestSuite;

void Test::do_test(bool cond, const std::string &lbl,
                   const char *fname, long lineno)
{
    if (!cond)
        do_fail(lbl, fname, lineno);
    else
        succeed_();
}

void Test::do_fail(const std::string &lbl, const char *fname,
                   long lineno)
{
    ++nFail;
    if (osptr)
    {
        *osptr << typeid(*this).name() << "failure: (" << lbl << ") . "
               << fname << " (line " << lineno << ")" << endl;
    }
}

long Test::report() const
{
    if (osptr)
    {
        *osptr << "Test \"" << typeid(*this).name()
               << "\":\n\tPassed: " << nPass
               << "\tFailed: " << nFail
               << endl;
    }
    return nFail;
}

#endif // TEST_IMPL